﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Diagnostics;

namespace LotroData
{
    /// <summary>
    /// Defines a LoTRO Item. Provides methods for loading from a data.lotro.com XML stream.
    /// </summary>
    [DebuggerDisplay("Name = {Name}, Id = {Id}")]
    public class LotroItem
    {
        private const string LorebookString = "http://lorebook.lotro.com/wiki/Special:LotroResource?id=";

        /// <summary>
        /// Gets or sets the name of the item
        /// </summary>
        /// <value>The name.</value>
        public string Name { get; private set; }
        /// <summary>
        /// Gets or sets the Lorebook ID of the item.
        /// </summary>
        /// <value>The id.</value>
        public int Id { get; private set; }
        /// <summary>
        /// Gets the lorebook URI of the item.
        /// </summary>
        /// <value>The lorebook URI.</value>
        public Uri LorebookUri { get { return new Uri(LorebookString + Id.ToString(CultureInfo.CurrentCulture)); } }
        /// <summary>
        /// Gets or sets the level of the item
        /// </summary>
        /// <value>The level.</value>
        public int Level { get; private set; }
        /// <summary>
        /// Gets or sets a value indicating whether this item is unique.
        /// </summary>
        /// <value><c>true</c> if this item is unique; otherwise, <c>false</c>.</value>
        public bool IsUnique { get; private set; }
        /// <summary>
        /// Gets or sets a value indicating whether this item is an item advancement item.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this item is an item advancement item; otherwise, <c>false</c>.
        /// </value>
        public bool IsItemAdvancement { get; private set; }
        /// <summary>
        /// Gets or sets a value indicating whether this item is consumed on use.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this item is consumed on use; otherwise, <c>false</c>.
        /// </value>
        public bool IsConsumedOnUse { get; private set; }
        /// <summary>
        /// Gets or sets the size of the stack of this item.
        /// </summary>
        /// <value>The size of the stack.</value>
        public int StackSize { get; private set; }
        /// <summary>
        /// Gets or sets the cost of the item.
        /// </summary>
        /// <value>The cost.</value>
        public LotroItemCost Cost { get; private set; }
        /// <summary>
        /// Gets or sets the icon URI of the item.
        /// </summary>
        /// <value>The icon URI.</value>
        public Uri IconUri { get; private set; }
        /// <summary>
        /// Gets or sets the quality of the item
        /// </summary>
        /// <value>The quality.</value>
        public LotroItemQuality Quality { get; private set; }
        /// <summary>
        /// Gets or sets the type of the item bind.
        /// </summary>
        /// <value>The type of the bind.</value>
        public LotroItemBindType BindType { get; private set; }
        /// <summary>
        /// Gets or sets the type of the item.
        /// </summary>
        /// <value>The type of the item.</value>
        public LotroItemType ItemType { get; private set; }
        /// <summary>
        /// Gets or sets the damage of the item.
        /// </summary>
        /// <value>The damage.</value>
        public LotroItemDamage Damage { get; private set; }
        /// <summary>
        /// Gets or sets the type of instrument.
        /// </summary>
        /// <value>The instrument.</value>
        public string Instrument { get; private set; }
        /// <summary>
        /// Gets or sets the cooldown of the item, in seconds.
        /// </summary>
        /// <value>The cooldown.</value>
        public int Cooldown { get; private set; }
        /// <summary>
        /// Gets or sets the decoration slot of the item.
        /// </summary>
        /// <value>The decoration slot.</value>
        public string DecorationSlot { get; private set; }
        /// <summary>
        /// Gets or sets the armour rating of the item.
        /// </summary>
        /// <value>The armour.</value>
        public int Armour { get; private set; }
        /// <summary>
        /// Gets or sets the durability of the item.
        /// </summary>
        /// <value>The durability.</value>
        public int Durability { get; private set; }
        /// <summary>
        /// Gets or sets the sturdiness of the item.
        /// </summary>
        /// <value>The sturdiness.</value>
        public LotroItemSturdiness Sturdiness { get; private set; }
        /// <summary>
        /// Gets or sets the requirements for the item.
        /// </summary>
        /// <value>The requirements.</value>
        public LotroItemRequirements Requirements { get; private set; }
        /// <summary>
        /// Gets or sets the description of the item.
        /// </summary>
        /// <value>The description.</value>
        public string Description { get; private set; }

        private List<string> _Effects;
        /// <summary>
        /// Gets the effects on the item.
        /// </summary>
        /// <value>The effects.</value>
        public ReadOnlyCollection<string> Effects { get { return new ReadOnlyCollection<string>(_Effects); } }

        /// <summary>
        /// Initializes a new instance of the <see cref="LotroItem"/> class.
        /// </summary>
        protected LotroItem()
        {
            this._Effects = new List<string>();
        }

        /// <summary>
        /// Loads from a data.lotro.com XML stream.
        /// </summary>
        /// <param name="element">The XML container.</param>
        /// <returns> A <see cref="LotroItem"/> with the loaded XML data.</returns>
        public static LotroItem LoadFromXml(XElement element)
        {
            if (element == null) throw new ArgumentNullException("element");

            var newItem = new LotroItem();

            //Full XML document
            if (element.Name == "apiresponse")
            {
                if (element.Element("item") == null) throw new ArgumentException("Invalid XML stream", "element");

                newItem.ParseXml(element.Element("item"));
            }
            else //Item XML section
            {
                if (element.Name != "item") throw new ArgumentException("Invalid XML element", "element");
                newItem.ParseXml(element);
            }

            return newItem;
        }

        /// <summary>
        /// Parses the XML.
        /// </summary>
        /// <param name="element">The element.</param>
        protected virtual void ParseXml(XElement element)
        {
            XElement localE;
            XAttribute localA;

            //Item Id
            if (element.Attribute("id") != null)
                this.Id = int.Parse(element.Attribute("id").Value, CultureInfo.CurrentCulture);
            else
                this.Id = int.Parse(element.Attribute("item_id").Value, CultureInfo.CurrentCulture);

            //Name
            this.Name = element.Attribute("name").Value;

            //Level
            localA = element.Attribute("level");
            if (localA != null)
                this.Level = int.Parse(localA.Value, CultureInfo.CurrentCulture);

            //Type
            localA = element.Attribute("type");
            if (localA != null)
                this.ItemType = localA.Value.ParseLotroEnum<LotroItemType>();

            //Quality
            localA = element.Attribute("quality");
            if (localA != null)
                this.Quality = localA.Value.ParseLotroEnum<LotroItemQuality>();

            //Is Unique?
            localA = element.Attribute("unique");
            if (localA != null)
                this.IsUnique = localA.Value.Equals("1");

            //Is Item Advancement
            localA = element.Attribute("isItemAdvancement");
            if (localA != null)
                this.IsItemAdvancement = localA.Value.Equals("1");

            //StackSize
            localA = element.Attribute("stackSize");
            if (localA != null)
                this.StackSize = int.Parse(localA.Value, CultureInfo.CurrentCulture);

            //Bind Type
            localA = element.Attribute("bindOnAcquire");
            if (localA != null)
            {
                var boa = localA.Value.Equals("1");
                var boe = element.Attribute("bindOnEquip").Value.Equals("1");
                if (boa)
                    this.BindType = LotroItemBindType.BindOnAcquire;
                else if (boe)
                    this.BindType = LotroItemBindType.BindOnEquip;
                else
                    this.BindType = LotroItemBindType.NotBound;
            }

            //Consumed on Use?
            localA = element.Attribute("consumedOnUse");
            if (localA != null)
                this.IsConsumedOnUse = localA.Value.Equals("1");

            //Cooldowm
            localA = element.Attribute("cooldown");
            if (localA != null)
                this.Cooldown = string.IsNullOrEmpty(localA.Value) ? 0 : int.Parse(localA.Value, CultureInfo.CurrentCulture);

            //Icon
            localA = element.Attribute("iconUrl");
            if (localA != null)
                this.IconUri = new Uri(localA.Value);

            //Decoration
            localA = element.Attribute("decoration");
            if (localA != null)
                this.Instrument = string.IsNullOrEmpty(localA.Value) ? null : localA.Value;

            //Instrument
            localA = element.Attribute("instrument");
            if (localA != null)
                this.Instrument = string.IsNullOrEmpty(localA.Value) ? null : localA.Value;

            //Requirements
            localE = element.Element("requirements");
            if (localE != null)
                this.Requirements = LotroItemRequirements.FromXml(localE);

            //Value
            localE = element.Element("value");
            if (localE != null)
                this.Cost = new LotroItemCost(int.Parse(localE.Attribute("baseCopperValue").Value, CultureInfo.CurrentCulture));

            //Durability
            localE = element.Element("durability");
            if (localE != null)
            {
                this.Durability = int.Parse(localE.Attribute("points").Value, CultureInfo.CurrentCulture);
                this.Sturdiness = localE.Attribute("type").Value.ParseLotroEnum<LotroItemSturdiness>();
            }

            //Damage
            localE = element.Element("damage");
            if (localE != null && localE.HasAttributes)
            {
                this.Damage = new LotroItemDamage(
                    double.Parse(localE.Attribute("dps").Value, CultureInfo.CurrentCulture),
                    double.Parse(localE.Attribute("maximum").Value, CultureInfo.CurrentCulture),
                    double.Parse(localE.Attribute("minimum").Value, CultureInfo.CurrentCulture),
                    localE.Attribute("type").Value);
            }


            //Armour
            localE = element.Element("armour");
            if (localE != null)
                if (localE.HasAttributes)
                    this.Armour = int.Parse(localE.Attribute("value").Value, CultureInfo.CurrentCulture);

            //Effects
            var effects = element.Element("effects");
            if (effects != null)
            {
                foreach (var effect in effects.Elements())
                {
                    this._Effects.Add(effect.Attribute("value").Value);
                }
            }

            //Set

            //Description
            localE = element.Element("description");
            if (localE != null)
                if (!string.IsNullOrEmpty(localE.Value))
                    this.Description = localE.Value;
        }
    }

    /// <summary>
    /// Defines a set of requirements that a <see cref="LotroItem"/> may have.
    /// </summary>
    public class LotroItemRequirements
    {
        /// <summary>
        /// Gets or sets the minimum level requirement.
        /// </summary>
        /// <value>The minimum level.</value>
        public int MinimumLevel { get; set; }

        /// <summary>
        /// Gets or sets the maximum level requirement.
        /// </summary>
        /// <value>The maximum level.</value>
        public int MaximumLevel { get; set; }
        /// <summary>
        /// Gets or sets the minimum glory rank requirement.
        /// </summary>
        /// <value>The glory rank.</value>
        public int GloryRank { get; set; }

        private List<string> _Traits;
        /// <summary>
        /// Gets or sets the traits requirements
        /// </summary>
        /// <value>The traits.</value>
        public ReadOnlyCollection<string> Traits { get { return new ReadOnlyCollection<string>(_Traits); } }

        private List<LotroClass> _Classes;
        /// <summary>
        /// Gets or sets the class requirements.
        /// </summary>
        /// <value>The classes.</value>
        public ReadOnlyCollection<LotroClass> Classes { get { return new ReadOnlyCollection<LotroClass>(_Classes); } }

        private List<LotroRace> _Races;
        /// <summary>
        /// Gets or sets the race requirements.
        /// </summary>
        /// <value>The races.</value>
        public ReadOnlyCollection<LotroRace> Races { get { return new ReadOnlyCollection<LotroRace>(_Races); } }

        private List<string> _Factions;
        /// <summary>
        /// Gets or sets the factions requirements.
        /// </summary>
        /// <value>The factions.</value>
        public ReadOnlyCollection<string> Factions { get { return new ReadOnlyCollection<string>(_Factions); } }

        /// <summary>
        /// Initializes a new instance of the <see cref="LotroItemRequirements"/> class.
        /// </summary>
        private LotroItemRequirements()
        {
            _Traits = new List<string>();
            _Classes = new List<LotroClass>();
            _Races = new List<LotroRace>();
            _Factions = new List<string>();
        }

        /// <summary>
        /// Creates and loads the <see cref="LotroItemRequirements"/> from an xml element.
        /// </summary>
        /// <param name="element">The XML element.</param>
        /// <returns></returns>
        public static LotroItemRequirements FromXml(XElement element)
        {
            if (element == null) throw new ArgumentNullException("element");
            if (element.Name != "requirements") throw new ArgumentException("Invalid XML stream", "element");

            var newReqs = new LotroItemRequirements();

            newReqs.ParseXml(element);

            return newReqs;
        }

        private void ParseXml(XElement element)
        {
            //Level
            if (element.Element("level").HasAttributes)
                this.MinimumLevel = int.Parse(element.Element("level").Attribute("minimum").Value, CultureInfo.CurrentCulture);

            //Glory
            if (element.Element("gloryRank").HasAttributes)
                this.GloryRank = int.Parse(element.Element("gloryRank").Attribute("minimum").Value, CultureInfo.CurrentCulture);

            //Classes
            foreach (var reqClass in element.Element("classes").Elements())
            {
                _Classes.Add(reqClass.Attribute("name").Value.ParseLotroEnum<LotroClass>());
            }
        }
    }

    /// <summary>
    /// Represents the Damage information that a <see cref="LotroItem"/> may have.
    /// </summary>
    public class LotroItemDamage
    {
        /// <summary>
        /// Gets or sets the DPS (Damage per second) value.
        /// </summary>
        /// <value>The DPS.</value>
        public double Dps { get; private set; }
        /// <summary>
        /// Gets or sets the maximum damage value.
        /// </summary>
        /// <value>The maximum.</value>
        public double Maximum { get; private set; }
        /// <summary>
        /// Gets or sets the minimum damage value.
        /// </summary>
        /// <value>The minimum.</value>
        public double Minimum { get; private set; }
        /// <summary>
        /// Gets or sets the type of the damage.
        /// </summary>
        /// <value>The type of the damage.</value>
        public string DamageType { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="LotroItemDamage"/> class.
        /// </summary>
        /// <param name="dps">The DPS value.</param>
        /// <param name="max">The maximum damage value.</param>
        /// <param name="min">The mininum damage value.</param>
        /// <param name="type">The damage type.</param>
        public LotroItemDamage(double dps, double max, double min, string type)
        {
            this.Dps = dps;
            this.Maximum = max;
            this.Minimum = min;
            this.DamageType = type;
        }
    }
}
